/*
 * This file is part of the Refran Library
 * Copyright (C) 2009 César Enrique García
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/** @file
 * $Author: cquike $
 * $Date: 2010-10-22 08:31:21 +0000 (Fri, 22 Oct 2010) $
 * $Revision: 233 $
 */

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE RecipeSuite
#define _GLIBCXX_USE_NANOSLEEP
#include <iostream>
#include <vector>
#include <thread>
#include <boost/test/unit_test.hpp>
#include <boost/thread.hpp>
#include "Recipe.h"
#include "DummyRecipe.h"
#include "HeavyCpuRecipe.h"
#include "Error.h"


BOOST_AUTO_TEST_SUITE(RecipeSuite)

BOOST_AUTO_TEST_CASE(instantiateRecipe)
{
    /* Recipe instantiation */
    BOOST_REQUIRE_NO_THROW(DummyRecipe recipe);
}

BOOST_AUTO_TEST_CASE(goWithoutInit)
{
    DummyRecipe recipe;
    /* Set of Frames is still not input, so it should not work */
    BOOST_REQUIRE_THROW(recipe.go(), Refran::Error);
}

BOOST_AUTO_TEST_CASE(getState)
{
    DummyRecipe recipe;
    BOOST_REQUIRE_EQUAL(recipe.getState(), Refran::RecipeState::INITIALIZING);
}

BOOST_AUTO_TEST_CASE(allGets)
{
    DummyRecipe recipe;
    BOOST_REQUIRE_NO_THROW(recipe.getAuthor());
    BOOST_REQUIRE_NO_THROW(recipe.getCopyright());
    BOOST_REQUIRE_NO_THROW(recipe.getDescription());
    BOOST_REQUIRE_NO_THROW(recipe.getEmail());
    BOOST_REQUIRE_NO_THROW(recipe.getName());
    BOOST_REQUIRE_NO_THROW(recipe.getVersion());
    BOOST_REQUIRE_NO_THROW(recipe.getSynopsis());
    BOOST_REQUIRE_NO_THROW(recipe.getURL());
}

BOOST_AUTO_TEST_CASE(allAssignedGets)
{
    DummyRecipe recipe;
    BOOST_REQUIRE_EQUAL(recipe.getAuthor(),"author");
    BOOST_REQUIRE_EQUAL(recipe.getCopyright(), "copyright");
    BOOST_REQUIRE_EQUAL(recipe.getDescription(), "longDesc");
    BOOST_REQUIRE_EQUAL(recipe.getEmail(),"email");
    BOOST_REQUIRE_EQUAL(recipe.getName(),"DummyRecipe");
    BOOST_REQUIRE_EQUAL(recipe.getVersion(), 1);
    BOOST_REQUIRE_EQUAL(recipe.getSynopsis(), "synopsis");
    BOOST_REQUIRE_EQUAL(recipe.getURL(), "http://url");
}

BOOST_AUTO_TEST_CASE(getProgressMultithread)
{
    int nThreads = 1000;
    DummyRecipe recipe;

    for(int i = 0; i< 10; ++i)
    {
        boost::thread_group pool;
        for(int j = 0; j< nThreads; ++j)
            pool.create_thread
                (boost::bind(&DummyRecipe::getProgress, &recipe));
        pool.join_all();
    }
    BOOST_REQUIRE_EQUAL(recipe.getState(), Refran::RecipeState::INITIALIZING);
}

BOOST_AUTO_TEST_CASE(getRunningProgressMultithread)
{
    int nThreads = 1000;
    HeavyCpuRecipe recipe;
    std::shared_ptr<Refran::FrameSet> frameset;
    recipe.setInputFrames(frameset);

    boost::thread_group pool;
    //One thread is reponsible for running the recipe
    pool.create_thread
        (boost::bind(&HeavyCpuRecipe::go, &recipe));

    //Other threads would access the progress
    for(int j = 0; j< nThreads; ++j)
        pool.create_thread
            (boost::bind(&DummyRecipe::getProgress, &recipe));
    //TODO: Check that the progress is between 0 and 1. The progress is not locked, therefore
    //unless the assignement within setProgress_ is atomic there is a data race. Checking that
    //it is between 0 and 1 will give confidence that it is atomic (because the variable does not
    //contain a corrupted value). Maybe this depends on the CPU architecture.
    //Apparently the proper way to make it atomic is using std::atomic<double> progress; but 
    //maybe that's present only in TR2, and not c++0x
    pool.join_all();
    BOOST_REQUIRE_EQUAL(recipe.getState(), Refran::RecipeState::FINISHED);
}

BOOST_AUTO_TEST_CASE(getRunningAccessorsMultithread)
{
    int nThreads = 200;
    HeavyCpuRecipe recipe;
    std::shared_ptr<Refran::FrameSet> frameset;
    recipe.setInputFrames(frameset);

    boost::thread_group pool;
    //One thread is reponsible for running the recipe
    pool.create_thread
        (boost::bind(&HeavyCpuRecipe::go, &recipe));

    //Other threads would access the progress
    for(int j = 0; j< nThreads; ++j)
    {
        pool.create_thread
            (boost::bind(&DummyRecipe::getState, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getVersion, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getName, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getSynopsis, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getDescription, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getAuthor, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getEmail, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getCopyright, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getURL, &recipe));
        pool.create_thread
            (boost::bind(&DummyRecipe::getInputFrames, &recipe));
    }
    pool.join_all();
    BOOST_REQUIRE_EQUAL(recipe.getState(), Refran::RecipeState::FINISHED);
}

BOOST_AUTO_TEST_CASE(goMultithreadSTL)
{
    const int nThreads = 10;
    HeavyCpuRecipe recipes[nThreads];
    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nThreads; ++i)
        recipes[i].setInputFrames(frameset);
    std::vector<std::thread> thread_group;

    //Launching the same recipe several times in several threads fails
    //Because after the first go() the Recipe is not in READY state and throws
    //an exception (which causes std::thread in turn to call std::terminate())
    //http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2320.html#Thread
    for(int i = 0; i < nThreads; ++i)
        thread_group.push_back
            (std::thread(boost::bind(&HeavyCpuRecipe::go, &(recipes[i]))));
    for(int i = 0; i < nThreads; ++i)
    {
        BOOST_REQUIRE_NE(recipes[i].getState(), Refran::RecipeState::INITIALIZING);
        recipes[i].getProgress();
        thread_group[i].join();
    }
    for(int i = 0; i < nThreads; ++i)
        BOOST_REQUIRE_EQUAL(recipes[i].getState(), Refran::RecipeState::FINISHED);
}

//Futures in Boost is not available until 1.41. In c++0x g++ are not still
//available
//BOOST_AUTO_TEST_CASE(goMultithreadFutureBoost)
//{
//    int nThreads = 10;
//    HeavyCpuRecipe recipe;
//    std::shared_ptr<Refran::FrameSet> frameset;
//    recipe.setInputFrames(frameset);
//    std::vector<boost::packaged_task<void>> task_group;
//
//    for(int i = 0; i < nThreads; ++i)
//        task_group.push_back
//            (boost::bind(&HeavyCpuRecipe::go, &(recipes[i])));
//    for(int i = 0; i < nThreads; ++i)
//    {
//        recipes[i].getProgress();
//        thread_group[i].join();
//    }
//}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(RecipeBenchmarksSuite)

BOOST_AUTO_TEST_CASE(instantiateDestroyBenchmark)
{
    int nExec = 100000;

    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nExec; ++i)
        DummyRecipe recipe;
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(instantiateGoDestroyBenchmark)
{
    int nExec = 100000;

    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nExec; ++i)
    {
        DummyRecipe recipe;
        recipe.setInputFrames(frameset);
        recipe.go();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(instantiateGoDestroy2BoostThreadBenchmark)
{
    const int nThreads = 2;
    int nExec = 50000;

    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nExec; ++i)
    {
        DummyRecipe recipes[nThreads];
        for(int j = 0; j < nThreads; ++j)
            recipes[j].setInputFrames(frameset);

        boost::thread_group pool;
        for(int j = 0; j < nThreads; ++j)
            pool.create_thread(boost::bind(&DummyRecipe::go, &(recipes[j])));
        pool.join_all();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(instantiateGoDestroy2STLThreadBenchmark)
{
    const int nThreads = 2;
    int nExec = 50000;

    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nExec; ++i)
    {
        DummyRecipe recipes[nThreads];
        std::thread threads[nThreads];
        for(int j = 0; j < nThreads; ++j)
            recipes[j].setInputFrames(frameset);

        for(int j = 0; j < nThreads; ++j)
            threads[j] = std::thread(std::bind(&DummyRecipe::go, &(recipes[j])));
        for(int j = 0; j < nThreads; ++j)
            threads[j].join();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(instantiateGoDestroy10BoostThreadBenchmark)
{
    const int nThreads = 10;
    int nExec = 10000;

    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nExec; ++i)
    {
        DummyRecipe recipes[nThreads];
        for(int j = 0; j < nThreads; ++j)
            recipes[j].setInputFrames(frameset);

        boost::thread_group pool;
        for(int j = 0; j < nThreads; ++j)
            pool.create_thread(boost::bind(&DummyRecipe::go, &(recipes[j])));
        pool.join_all();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(instantiateGoDestroy100BoostThreadBenchmark)
{
    const int nThreads = 100;
    int nExec = 1000;

    std::shared_ptr<Refran::FrameSet> frameset;
    for(int i = 0; i < nExec; ++i)
    {
        DummyRecipe recipes[nThreads];
        for(int j = 0; j < nThreads; ++j)
            recipes[j].setInputFrames(frameset);

        boost::thread_group pool;
        for(int j = 0; j < nThreads; ++j)
            pool.create_thread(boost::bind(&DummyRecipe::go, &(recipes[j])));
        pool.join_all();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(getState1000BoostThreadBenchmark)
{
    int nThreads = 1000;
    DummyRecipe recipe;

    for(int i = 0; i< 10; ++i)
    {
        boost::thread_group pool;
        for(int j = 0; j< nThreads; ++j)
            pool.create_thread
                (boost::bind(&DummyRecipe::getState, &recipe));
        pool.join_all();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(getProgress1000BoostThreadBenchmark)
{
    int nThreads = 1000;
    DummyRecipe recipe;

    for(int i = 0; i< 10; ++i)
    {
        boost::thread_group pool;
        for(int j = 0; j< nThreads; ++j)
            pool.create_thread
                (boost::bind(&DummyRecipe::getProgress, &recipe));
        pool.join_all();
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_SUITE_END()
